import dayjs from 'dayjs';
import got from 'got';
import { isObjectLike } from 'lodash';
import TradeAccountService from 'ROOT/module/trade-account/TradeAccount.service';
import WatchService from 'ROOT/module/watch/watch.service';
import * as AutoOrderHistoryService from "ROOT/module/order-history/AutoOrderHistory.service";
import SymbolOrderConfigService from 'ROOT/module/symbol-order-config/SymbolOrderConfig.service';
import SymbolFactor from 'ROOT/module/symbol-factor/SymbolFactor.service';
import UserService from './module/user/user.service';
import KLineHistoryService from "ROOT/module/kline-history/KLineHistory.service";
import { KLineDTO } from 'ROOT/module/kline-history/KLineHistory.entity';
import { dbEvent } from "ROOT/db/orm";
import { robot } from "ROOT/common/DingRobot";
import { errLogger, outLogger } from 'ROOT/common/logger';
import { Trader, DataHandler, sample, getSpotSymbol, OrderStatus, AutoTradeType  } from './trader';
import { BinanceTrader, binancePriceConverter } from './binance/BinanceTrader';
import { binanceCommon } from './binance/BinanceCommon';
import { EventTypes, ws_event } from 'ROOT/ws/socketIO';
import { SocketFrom } from 'ROOT/interface/ws';
import { getInterval, transformCSVToJSON } from 'ROOT/utils';
import { AUTHORITY, ENV_IS_DEV, ENV_IS_TAIN, ONLINE } from 'ROOT/constants/common';
import SymbolOrderConfigEntity from './module/symbol-order-config/SymbolOrderConfig.entity';
import { OrderSide } from './lib/quant';
import { cryptoHelper } from './utils/crypto';

class TraderRobot {
  public trader: Trader;
  public dataHander: DataHandler; 
  private symbolListenerMap = new Map<string, Array<() => void>>();
  private count = 0;
  private orderTimer: NodeJS.Timeout;
  constructor() {
    this.trader = new Trader();
    this.dataHander = this.initDataHander();
  }
  run() {
    this.loadSymbols();
    this.loadUsers();
  }
  async loadSymbols() {
    // const startDate = await KLineHistoryService.findEarliestDataMonth('STORJUSDT', dayjs().subtract(3, 'year').startOf('year'));
    // outLogger.info('STORJUSDT startYearAndMonth', startDate.format('YYYY-MM-DD HH:mm:ss'));
    const watchEntityList = await WatchService.find({enable: true});
    for (let i = 0; i < watchEntityList.length; i++) {
      const watchEntity = watchEntityList[i];
      const symbol = watchEntity.symbol.toUpperCase();
      this.loadVector(symbol);
      this.addSymbol(symbol).catch(err => {
        errLogger.error(err.message);
      });
      // 作为主信号提示
      if (watchEntity.symbol === 'ETHUSDT' || watchEntity.symbol === 'BTCUSDT') {
        this.trader.tradingStrategy
        .addCustomStrategy(String(0), symbol, {code: watchEntity.strategy.strategy})
        .onSignal((signal, kline) => {
          const dateStr = dayjs(new Date()).utcOffset(8).format("MM/DD HH:mm:ss");
          outLogger.info(`TraderRobot ${signal} -> ${kline.symbol}(${kline.close}) \n ${dateStr}`);
          robot.markdown(
            `信号通知`,
            `(重要,日线级别信号)${signal === 'BUY' ? '<font color=#5EBF7E>BUY</font>' : '<font color=#F6465D>SELL</font>'} -> ${kline.symbol}(${kline.close}) \n ${dateStr}`
          );
        });
      }
    }
  }
  async loadUsers() {
    const users = await UserService.find({});
    for (const user of users) {
      if (user.role === AUTHORITY.admin) {
        TradeAccountService.findOne({ exchange: 'binance', userid: user.id }, true).then((exchangeAccount) => {
          if (exchangeAccount.access_key) {
              exchangeAccount.access_key = cryptoHelper.decrypt(exchangeAccount.access_key)
              exchangeAccount.secret_key = cryptoHelper.decrypt(exchangeAccount.secret_key)
          }
     
          binanceCommon.updateSDK({
            access_key: exchangeAccount.secret_key,
            secret_key: exchangeAccount.access_key,
          });
        }); 
      }
      // 加载订单配置
      const orderConfigList = await SymbolOrderConfigService.find({ userid: user.id, enabled: true }, true);
      for (const orderConfig of orderConfigList) {
        await this.addOrderConfig(orderConfig);
      }
    }
    this.syncOrderFromExchangeTask();
  }
  async loadVector(symbol: string) {
    const [list_1d, list_4h, list_15m] = await Promise.all([
      SymbolFactor.find({symbol: symbol, period: '1d'}),
      SymbolFactor.find({symbol: symbol, period: '4h'}), 
      SymbolFactor.find({symbol: symbol, period: '15m'})
    ]);
    outLogger.info(`SymbolFactor find(${symbol}) ->  1d:${list_1d.length} - 4h:${list_4h.length} - 15m:${list_15m.length}`);

    sample.setVectors(symbol, '1d',  list_1d);
    sample.setVectors(symbol, '4h',  list_4h);
    sample.setVectors(symbol, '15m',  list_15m);

  }

  async fillMissData(symbol: string, newSymbol?: boolean) {
    const period = '15m';
    if (ONLINE) {
      if (newSymbol) {
        const startDate = await KLineHistoryService.findEarliestDataMonth(symbol, dayjs().subtract(3, 'year').startOf('year'));
        await KLineHistoryService.syncKLineData(symbol, startDate.toDate(), dayjs().toDate(), period);
      } else {
        await KLineHistoryService.fillMissData(symbol, dayjs().subtract(6, 'day').toDate(), dayjs().toDate(), period);
      }
    }
    return undefined;
  }
  getSymbolWatchCount(userid: number, symbol: string, tradeType: AutoTradeType) {
    const trader = this.trader.findUserTrader(userid);
    if (trader) {
      return trader.riskModel.getWatchPostionCount(symbol, tradeType);
    }
  }
  async getSymbolHistory(symbol: string) {
    let traderList = [] as KLineDTO[];
    const [start, end] = getInterval('1d', 120);
    if (ENV_IS_DEV) {
      const { body } = await got.get<{ data: string }>("http://trader.8and1.cn/api/kline-history", {
        method: "GET",
        searchParams: {
          start: start.valueOf(),
          end: end.valueOf(),
          symbol: symbol
        },
        responseType: "json"
      });
      traderList = transformCSVToJSON(body.data) as KLineDTO[];
      outLogger.info(`kline-history(${symbol})`, traderList.length);
    } else {
      traderList = await KLineHistoryService.find({
        start: start.valueOf(),
        end: end.valueOf(),
        symbol: symbol
      });
      outLogger.info(`kline-history:local(${symbol})`, traderList.length);
    }
    return traderList;
  }
  async addSymbol(symbol: string, newSymbol?: boolean) {
    outLogger.info(`addSymbol ${symbol} newSymbol: ${Boolean(newSymbol)}`)
    symbol = symbol.toUpperCase();
    const [ history, _ ] = await Promise.all([this.getSymbolHistory(symbol), this.fillMissData(symbol, newSymbol)]);
    if (this.dataHander == undefined) {
      throw Error("dataHander 需要初始化");
    }
    // 提前计算历史数据
    history.forEach((trade) => {
      const { time, ...reset } = trade;
      this.dataHander.handleHistory({
        timestamp: dayjs(time).valueOf(),
        ...reset,
      })
    });
    this.subscribeExchangeData(symbol);
  }
  removeSymbol(symbol: string) {
    outLogger.info(`removeSymbol ${symbol}`);
    const unsubs = this.symbolListenerMap.get(symbol);
    if (!unsubs) {
      outLogger.error(`${symbol} 不存在`);
      return
    }
    // 取消订阅
    for (let i = 0; i < unsubs.length; i++) {
      const unsub = unsubs[i];
      if (typeof unsub === 'function') {
        unsub();
      }
    }
    this.trader.removeSymbol(symbol);
  }

  /**
   * 添加自动交易订单配置
   */
  async addOrderConfig (orderConfig: SymbolOrderConfigEntity) {
    const userid = orderConfig.userid;
    outLogger.info(`userid: ${userid} addOrderConfig`, orderConfig.symbol)      
    // 首次需要添加 交易所的trader
    if (!this.trader.findUserTrader(userid)) {

      const [exchangeAccount, orderHistory, user] = await Promise.all([
        TradeAccountService.findOne({ userid: orderConfig.userid }, true),
        AutoOrderHistoryService.find({ userid: userid}, {
          pageSize: 20,
          current: 1,
        }),
        UserService.findOne({id: userid})
      ])
      if (!exchangeAccount) {
        return
      }
      if (exchangeAccount.access_key) {
        try {
          exchangeAccount.access_key = cryptoHelper.decrypt(exchangeAccount.access_key)
          exchangeAccount.secret_key = cryptoHelper.decrypt(exchangeAccount.secret_key)
        } catch (error) {
          console.error(error)
        }
      }
      const exchangeTrader = new BinanceTrader(userid, binanceCommon, {
        dataHander: this.dataHander,
        access_key: exchangeAccount.secret_key,
        secret_key: exchangeAccount.access_key,
        username: user.user
      });
      exchangeTrader.onOrderNotification(this.onOrderCreate);
      exchangeTrader.watchOrder(orderHistory.list);
  
      this.trader.addExchangeTrader(exchangeTrader);
      this.trader.watchOrderRisk(userid, orderConfig.symbol, orderConfig.trade_type);
    }
    this.trader.addUserOrder(orderConfig);
  }

  async updateOrderConfig (config: SymbolOrderConfigEntity) {
    this.trader.updateUserOrder(config);
  }
  removeOrderConfig(userid: number, symbol: string) {
    this.trader.removeUserSymbolStrategy(userid, symbol);
  }
 
  subscribeExchangeData(symbol: string) {
    if (ENV_IS_TAIN) {
      console.log(`subscribeExchangeData stop -> ENV_IS_TAIN: ${ENV_IS_TAIN}`);
      return
    }
    const unsubs: Array<() => void> = [];
    unsubs.push(binanceCommon.binanceWebSocket.subscribeSpotAggregateTrades(symbol, (data) => {
      if (data.symbol) {
        this.dataHander.handleTrades([{
          symbol: data.symbol,
          quantity: data.quantity,
          direction: data.maker ? 'SELL' : 'BUY',
          eventTime: data.eventTime,
          price: data.price
        }])
      }
    }));

    unsubs.push(binanceCommon.binanceWebSocket.subscribeSpotKline(symbol, '15m', (data) => {
      binancePriceConverter.setBaseSymbolPrice(symbol, data.close);
      this.dataHander.handleCandlesticks(data)
      this.dataHander.orderBook.updatePrice(symbol, data.close);
    }));

    unsubs.push(binanceCommon.binanceWebSocket.subscribeSpotDepth(symbol, (data) => {
      this.dataHander.handleDepth({
        symbol: data.s,
        asks: data.a,
        bids: data.b,
        timestamp: data.E
      })
    }));
    unsubs.push(binanceCommon.binanceWebSocket.subscribeUSDMKlines(symbol, '15m', (data) => {
      this.dataHander.futruesOrderBook.updatePrice(symbol, data.close);
      this.trader.updatePrice('futures', symbol, data.close);
    }));
    binanceCommon.getDeliverySymolInfo(symbol).then(data => {
      if (!data || !data.symbol) {
        return
      }
      const deliverySymbol = data.symbol
      unsubs.push(binanceCommon.binanceWebSocket.subscribeDeliveryKlines(deliverySymbol, '15m', (data) => {
        this.trader.updatePrice('delivery', deliverySymbol, data.close);
      }));
    })
    
    unsubs.push(binanceCommon.binanceWebSocket.subscribeUSDMDepth(symbol, (data) => {
      if (!Array.isArray(data.a)) {
        errLogger.error(`${symbol} data.a:${data.a}`);
      }
      this.dataHander.handleFutruesDepth({
        symbol: data.s,
        asks: data.a,
        bids: data.b,
        timestamp: data.E
      })
    }));
    
    this.symbolListenerMap.set(symbol, unsubs);
  }
  initDataHander() {
    const dataHander = new DataHandler('real');

    dataHander.on('interval:15m', (data) => {
      this.trader.addData(data, '15m');
      if (!data.isMock) {
        ws_event.emit("send-to-client", {
          from: SocketFrom.server,
          type: EventTypes.KLine,
          data: data,
          period: '15m'
        });
        if (ONLINE) {
          KLineHistoryService.create({
            sell: data.sell,
            buy: data.buy,
            symbol: data.symbol,
            close: data.close,
            time: new Date(data.timestamp),
            open: data.open,
            high: data.high,
            low: data.low,
            volume: data.volume,
            bid_depth_count: data.bids.length,
            ask_depth_count: data.asks.length,
          }).catch((err) => { errLogger.error(err.message) });
        }
      }
    });
    dataHander.on('interval:4h', (data) => {
      this.trader.addData(data, '4h');
    });
    dataHander.on('interval:1d', (data) => {
      this.trader.addData(data, '1d');
    });

    dataHander.on('update:15m', ({bids, asks, ...klineData}) => {
      if (klineData.symbol === undefined || klineData.close === undefined) {
        return
      }
      this.trader.updateData(klineData, '15m');
      if (!klineData.isMock) {
        ws_event.emit("send-to-client", {
          from: SocketFrom.server,
          type: EventTypes.Depth,
          data: {
            symbol: klineData.symbol,
            bids: bids,
            asks: asks,
          },
        });

        ws_event.emit("send-to-client", {
          from: SocketFrom.server,
          type: EventTypes.KLine,
          data: klineData,
          period: '15m'
        });
        if (this.count > 0) {
          outLogger.info('update:5m', bids.length)
          this.count--;
        }
      }
    });

    dataHander.on('update:4h', ({bids, asks, ...klineData}) => {
      this.trader.updateData(klineData, '4h');
      if (!klineData.isMock) {
        ws_event.emit("send-to-client", {
          from: SocketFrom.server,
          type: EventTypes.KLine,
          data: klineData,
          period: '4h'
        });
      }
    });
  
    dataHander.on('update:1d', ({bids, asks, ...klineData}) => {
      this.trader.updateData(klineData, '1d');
      if (!klineData.isMock) {
        ws_event.emit("send-to-client", {
          from: SocketFrom.server,
          type: EventTypes.KLine,
          data: klineData,
          period: '1d'
        });
      }
    });

    return dataHander
  }
  onOrderCreate = ({ symbol, price, quantity, action, tradeType, orderId, status, userid, orderType, raw = {}}) => {
    AutoOrderHistoryService.create({
      datetime: new Date(),
      symbol: symbol,
      price: Number(price),
      amount: Number(quantity),
      userid: userid,
      status: (orderId || status) ? status : 'FAIL',
      order_id: String(orderId ? orderId : ""),
      raw: JSON.stringify({orderType: orderType, ...raw}),
      action: action,
      trade_type: tradeType
    }).catch((err) => errLogger.error(err.message));
    const dateStr = dayjs(new Date()).utcOffset(8).format("MM/DD HH:mm:ss");
    outLogger.info(` userid: ${userid}  ${action} -> ${symbol}(${price})${dateStr} status: ${status}`);
    // userService.findOne({id: userid }).then((userInfo) => {
  
    // })
  }
  
  async syncOrderFromExchange(userid: number) {
    const { list: historys } = await AutoOrderHistoryService.find({ userid: userid },
      {
        pageSize: 20,
        current: 1
      }
    );
    const userTrader = this.trader.findUserTrader(userid);
    if (!userTrader) {
      // outLogger.info(`syncOrderFromExchange -> findUserTrader fail -> ${userid}`)
      return
    }
    const filterList = historys.filter(item => {
      return item.order_id && (item.status === "" || item.status === "NEW" || item.status === 'PARTIALLY_FILLED')
    });

    for (let i = 0; i < filterList.length; i++) {
      const order = historys[i];
      const symbolOrderConfig = await SymbolOrderConfigService.findOne({ symbol: order.symbol, userid });
      if (!symbolOrderConfig || !symbolOrderConfig.enabled) {
        continue;
      }
      let data = await userTrader.getOrder(order);
      if (isObjectLike(data) && data.status) {
        // 超时取消
        if (data.status === "NEW" && (Date.now() - data.time) > 1000 * 60 * 60 * 6) {
          outLogger.info(`order timeout -> ${order.order_id} ${order.symbol}`);
          data = await userTrader.cancelOrder(order.symbol, order.order_id, order.trade_type);
        } else if (data.price) {
          const avgPrice = (data as any).avgPrice ? Number((data as any).avgPrice) : Number(data.price);
          if (avgPrice != 0) {
            order.price = avgPrice ? avgPrice : Number(data.price)
          }
        }
        // 成交了
        if (order.status === 'NEW' && order?.raw.includes('LIMIT') && data.status === 'FILLED') {
          outLogger.info('成交了，但没watch到')
          await this.trader.watchOrderRisk(userid, order.symbol, order.trade_type);
        }
        order.status = data.status;
        await AutoOrderHistoryService.updateOne(
          { id: order.id },
          order
        );
      }
    }
    
    historys.forEach(item => {
      // 手动取消了机器人开单后
      if (item.order_id && item.status === 'CANCELED') {
        userTrader.orderWatch.removeOrder({ symbol: item.symbol, clientOrderId: item.order_id, tradeType: item.trade_type })
      }
    });
  }
  async syncOrderFromExchangeTask() {
    clearInterval(this.orderTimer);
    this.orderTimer = setInterval(async () => {
      const userList = await UserService.find({});
      for (const user of userList) {
        await this.syncOrderFromExchange(user.id);
      }
    }, 1000 * 60 * 20);
  }
}

export const traderRobot = new TraderRobot();

dbEvent.on('connected', () => {
  traderRobot.run();
});


// Exchange(datahandler), TradeSymbol(), Strategy(), ExchangeTrader() 

// class Trader {
//   exchange: Exchange[];
//   symbols: TradeSymbol[];
//   strategy: Strategy[];
//   exchangeTrader: ExchangeTrader[];
// }